/*
* Copyright 2012-2014 Brian Matthews
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.btmatthews.leabharlann.view;
import com.btmatthews.atlas.jcr.JCRAccessor;
import com.btmatthews.atlas.jcr.NodeCallback;
import com.btmatthews.atlas.jcr.RepositoryAccessException;
import com.btmatthews.leabharlann.domain.FileContent;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;
import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Session;
import java.io.IOException;
import java.util.Calendar;
import java.util.Collections;
/**
* A Spring MVC message converter that is used to write a file's contents to the HTTP servlet response's output stream.
*
* @author <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
* @since 1.0.0
*/
@Component
public class FileContentMessageConverter extends AbstractHttpMessageConverter<FileContent> {
/**
* The {@link JCRAccessor} API object used to access the Java Content Repository.
*/
private JCRAccessor jcrAccessor;
/**
* Inject the {@link JCRAccessor} API object used to access the Java Content Repository.
*
* @param accessor The {@link JCRAccessor} API object.
*/
@Autowired
public void setJcrAccessor(final JCRAccessor accessor) {
jcrAccessor = accessor;
}
/**
* Return true if this message converter supports the class {@code clazz}.
*
* @param clazz A class description.
* @return Returns true {@code clazz} can be assigned to {@link FileContent}.
*/
@Override
protected boolean supports(final Class<?> clazz) {
return FileContent.class.isAssignableFrom(clazz);
}
/**
* Always throws {@link HttpMessageNotReadableException} to indicate that reading {@link FileContent} descriptors is
* not supported.
*
* @param clazz A class description.
* @param inputMessage Used to access the servlet request headers and input stream.
* @return Always throws an exception.
* @throws HttpMessageNotReadableException Indicates that a {@link FileContent} cannot be read.
*/
@Override
protected FileContent readInternal(final Class<? extends FileContent> clazz, final HttpInputMessage inputMessage) throws HttpMessageNotReadableException {
throw new HttpMessageNotReadableException("Cannot read messages of this type");
}
/**
* Set the HTTP headers on the servlet response and stream the file contents to the servlet response's output stream.
*
* @param fileContent Describes the file content.
* @param outputMessage Used to access the servlet response headers and output stream.
* @throws IOException If there was an error streaming the file content.
* @throws HttpMessageNotWritableException If there was problem retrieving the file content from the Java Content Repository.
*/
@Override
protected void writeInternal(final FileContent fileContent, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
try {
jcrAccessor.withNodeId(fileContent.getWorkspace(), fileContent.getId(), new NodeCallback() {
@Override
public Object doInSessionWithNode(Session session, Node node) throws Exception {
final Node resourceNode = node.getNode(Node.JCR_CONTENT);
final String mimeType = jcrAccessor.getStringProperty(resourceNode, Property.JCR_MIMETYPE);
final Calendar lastModified = jcrAccessor.getCalendarProperty(resourceNode, Property.JCR_LAST_MODIFIED);
final Binary data = jcrAccessor.getBinaryProperty(resourceNode, Property.JCR_DATA);
if (jcrAccessor.hasProperty(resourceNode, Property.JCR_ENCODING)) {
final String encoding = jcrAccessor.getStringProperty(resourceNode, Property.JCR_ENCODING);
outputMessage.getHeaders().setContentType(new MediaType(MediaType.valueOf(mimeType), Collections.singletonMap("charset", encoding)));
} else {
outputMessage.getHeaders().setContentType(MediaType.valueOf(mimeType));
}
outputMessage.getHeaders().setContentLength(data.getSize());
if (lastModified != null) {
outputMessage.getHeaders().setLastModified(lastModified.getTimeInMillis());
}
outputMessage.getHeaders().set("Content-Disposition", "attachment;filename=" + node.getName());
IOUtils.copy(data.getStream(), outputMessage.getBody());
return null;
}
});
} catch (final RepositoryAccessException e) {
throw new HttpMessageNotWritableException(e.getLocalizedMessage());
}
}
}